package com.zp.oauth2.gateway.filter;

import cn.hutool.core.codec.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.zp.common.base.constant.Atom;
import com.zp.common.base.constant.ResultCode;
import com.zp.common.base.resp.BaseResp;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
 * 自定义全局过滤器
 *
 *  1.白名单放行
 *  2.检查令牌是否在黑名单中
 *  3.解析token的信传递用户信息给下游服务
 * @author zp
 */
@Component
@Slf4j
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    /**
     * JWT令牌的服务
     */
    @Resource
    private TokenStore tokenStore;

    @Resource
    private StringRedisTemplate stringRedisTemplate;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String requestUrl = exchange.getRequest().getPath().value();
        //1、白名单放行，比如授权服务、静态资源.....
        if (checkUrls(Atom.EXCLUSION_PATHS,requestUrl)){
            return chain.filter(exchange);
        }

        //2、获取前端请求携带的token
        String token = getToken(exchange);

        //3 解析token获取用户信息传递给下游服务
        OAuth2AccessToken oAuth2AccessToken;
        try {
            //解析token，使用tokenStore
            oAuth2AccessToken = tokenStore.readAccessToken(token);
            Map<String, Object> additionalInformation = oAuth2AccessToken.getAdditionalInformation();
            //令牌的唯一ID
            String jti=additionalInformation.get(Atom.JTI).toString();

            //查看黑名单中是否存在这个jti，如果存在则这个令牌不能用
            Boolean hasKey = stringRedisTemplate.hasKey(Atom.JTI_KEY_PREFIX + jti);
            if (hasKey)
                return invalidTokenMono(exchange);
            //取出用户身份信息
            String user_name = additionalInformation.get("user_name").toString();
            //获取用户权限
            List<String> authorities = (List<String>) additionalInformation.get("authorities");
            //从additionalInformation取出想要的用户信息传递给下游服务
            String userId = additionalInformation.get(Atom.USER_ID).toString();
            String telephone = additionalInformation.get(Atom.TELEPHONE).toString();
            String email = additionalInformation.get(Atom.EMAIL).toString();

            JSONObject jsonObject=new JSONObject();
            jsonObject.put(Atom.PRINCIPAL_NAME, user_name);
            jsonObject.put(Atom.AUTHORITIES_NAME,authorities);
            jsonObject.put(Atom.TELEPHONE,telephone);
            jsonObject.put(Atom.EMAIL,email);
            //过期时间，单位秒
            jsonObject.put(Atom.EXPR,oAuth2AccessToken.getExpiresIn());
            jsonObject.put(Atom.JTI,jti);
            //封装到JSON数据中
            jsonObject.put(Atom.USER_ID, userId);
            //将解析后的token加密放入请求头中，方便下游微服务解析获取用户信息
            String base64 = Base64.encode(jsonObject.toJSONString());
            //放入请求头中
            ServerHttpRequest tokenRequest = exchange.getRequest().mutate().header(Atom.TOKEN_NAME, base64).build();
            ServerWebExchange build = exchange.mutate().request(tokenRequest).build();
            return chain.filter(build);
        } catch (InvalidTokenException e) {
            log.error("token解析失败--{}",e.getMessage());
            //解析token异常，直接返回token无效
            return invalidTokenMono(exchange);
        }


    }

    @Override
    public int getOrder() {
        return 0;
    }

    /**
     * 对url进行校验匹配
     */
    private boolean checkUrls(String[] urls,String path){
        AntPathMatcher pathMatcher = new AntPathMatcher();
        for (String url : urls) {
            if (pathMatcher.match(url,path))
                return true;
        }
        return false;
    }

    /**
     * 从请求头中获取Token
     */
    private String getToken(ServerWebExchange exchange) {
        String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (StringUtils.isBlank(tokenStr)) {
            return null;
        }
        String token = tokenStr.split(" ")[1];
        if (StringUtils.isBlank(token)) {
            return null;
        }
        return token;
    }

    /**
     * 无效的token
     */
    private Mono<Void> invalidTokenMono(ServerWebExchange exchange) {
        log.info("token解析失败");
        return buildReturnMono(new BaseResp(ResultCode.TOKEN_INVALID), exchange);
    }


    private Mono<Void> buildReturnMono(BaseResp resultMsg, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        byte[] bits = JSON.toJSONString(resultMsg).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset:utf-8");
        return response.writeWith(Mono.just(buffer));
    }
}
